// Copyright 2009 John Diss (www.newgrove.com)
/*
 * This license governs use of the accompanying software. If you use the software, you
 * accept this license. If you do not accept the license, do not use the software.
 * 
 * 1. Definitions
 * The terms "reproduce," "reproduction," "derivative works," and "distribution" have the
 * same meaning here as under U.S. copyright law.
 * A "contribution" is the original software, or any additions or changes to the software.
 * A "contributor" is any person that distributes its contribution under this license.
 * "Licensed patents" are a contributor's patent claims that read directly on its contribution.
 * 
 * 2. Grant of Rights
 * (A) Copyright Grant- Subject to the terms of this license, including the license conditions 
 *     and limitations in section 3, each contributor grants you a non-exclusive, worldwide, 
 *     royalty-free copyright license to reproduce its contribution, prepare derivative works 
 *     of its contribution, and distribute its contribution or any derivative works that you create.
 * (B) Patent Grant- Subject to the terms of this license, including the license conditions and 
 *     limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free 
 *     license under its licensed patents to make, have made, use, sell, offer for sale, import, 
 *     and/or otherwise dispose of its contribution in the software or derivative works of the 
 *     contribution in the software.
 * 
 * 3. Conditions and Limitations
 * (A) No Trademark License- This license does not grant you rights to use any contributors' 
 *     name, logo, or trademarks.
 * (B) If you bring a patent claim against any contributor over patents that you claim are 
 *     infringed by the software, your patent license from such contributor to the software 
 *     ends automatically.
 * (C) If you distribute any portion of the software, you must retain all copyright, patent, 
 *     trademark, and attribution notices that are present in the software.
 * (D) If you distribute any portion of the software in source code form, you may do so only under 
 *     this license by including a complete copy of this license with your distribution. If you 
 *     distribute any portion of the software in compiled or object code form, you may only do so 
 *     under a license that complies with this license.
 * (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no 
 *     express warranties, guarantees or conditions. You may have additional consumer rights under 
 *     your local laws which this license cannot change. To the extent permitted under your local laws, 
 *     the contributors exclude the implied warranties of merchantability, fitness for a particular 
 *     purpose and non-infringement.
 * 
 */
using System;
using System.Collections.Generic;

namespace NPack.Numeric
{
    internal static class MethodLookup
    {
        private static readonly Dictionary<Type, INumericOperations> ConstructorCache;
        private static readonly Dictionary<OperatorCacheKey, INumericOperations> OperatorCache;

        static MethodLookup()
        {
            OperatorCache = new Dictionary<OperatorCacheKey, INumericOperations>
                                {
                                    {
                                        new OperatorCacheKey
                                            {
                                                Left = typeof (double),
                                                Right = typeof (double),
                                                Result = typeof (double)
                                            }, new NumericOperationsDouble()
                                        }
                                    ,
                                    {
                                        new OperatorCacheKey
                                            {
                                                Left = typeof (Single),
                                                Right = typeof (Single),
                                                Result = typeof (Single)
                                            },
                                        new NumericOperationsSingle()
                                        },
                                    {
                                        new OperatorCacheKey
                                            {
                                                Left = typeof (Int16),
                                                Right = typeof (Int16),
                                                Result = typeof (Int16)
                                            },
                                        new NumericOperationsInt16()
                                        },
                                    {
                                        new OperatorCacheKey
                                            {
                                                Left = typeof (Int32),
                                                Right = typeof (Int32),
                                                Result = typeof (Int32)
                                            },
                                        new NumericOperationsInt32()
                                        },
                                    {
                                        new OperatorCacheKey
                                            {
                                                Left = typeof (Int64),
                                                Right = typeof (Int64),
                                                Result = typeof (Int64)
                                            },
                                        new NumericOperationsInt64()
                                        }
                                    ,
                                    {
                                        new OperatorCacheKey
                                            {
                                                Left = typeof (Decimal),
                                                Right = typeof (Decimal),
                                                Result = typeof (Decimal)
                                            },
                                        new NumericOperationsDecimal()
                                        }/*,
                                    {
                                        new OperatorCacheKey
                                            {
                                                Left = typeof (Real<Double>),
                                                Right = typeof (Real<Double>),
                                                Result = typeof (Real<Double>)
                                            }, new NumericOperationsRealT<Double>()
                                        },
                                    {
                                        new OperatorCacheKey
                                            {
                                                Left = typeof (Real<Int32>),
                                                Right = typeof (Real<Int32>),
                                                Result = typeof (Real<Int32>)
                                            }, new NumericOperationsRealT<Int32>()
                                        },
                                    {
                                        new OperatorCacheKey
                                            {
                                                Left = typeof (Real<Single>),
                                                Right = typeof (Real<Single>),
                                                Result = typeof (Real<Single>)
                                            }, new NumericOperationsRealT<Single>()
                                        },
                                    {
                                        new OperatorCacheKey
                                            {
                                                Left = typeof (Real<Int16>),
                                                Right = typeof (Real<Int16>),
                                                Result = typeof (Real<Int16>)
                                            }, new NumericOperationsRealT<Int16>()
                                        },
                                    {
                                        new OperatorCacheKey
                                            {
                                                Left = typeof (Real<Int64>),
                                                Right = typeof (Real<Int64>),
                                                Result = typeof (Real<Int64>)
                                            }, new NumericOperationsRealT<Int64>()
                                        }*/
                                };

            ConstructorCache = new Dictionary<Type, INumericOperations>
                                   {
                                       {
                                           typeof (Int32), new NumericOperationsInt32()
                                           },
                                       {
                                           typeof (Int64), new NumericOperationsInt64()
                                           },
                                       {
                                           typeof (Single), new NumericOperationsSingle()
                                           },
                                       {
                                           typeof (Double), new NumericOperationsDouble()
                                           },
                                       {
                                           typeof (Decimal), new NumericOperationsDecimal()
                                           }
                                   };
        }

        public static Func<TLeft, TRight, TResult> Lookup<TLeft, TRight, TResult, TOwner>(Operator op)
        {
            INumericOperations ops;
            //jd:perf using statically compiled methods here bumps performance slightly
            if (OperatorCache.TryGetValue(new OperatorCacheKey
                                              {
                                                  Left = typeof(TLeft),
                                                  Right = typeof(TRight),
                                                  Result = typeof(TResult)
                                              }, out ops))
            {
                INumericOperations<TLeft, TRight, TResult> cops =
                    ops as INumericOperations<TLeft, TRight, TResult>;
                if (cops != null)
                {
                    switch (op)
                    {
                        case Operator.Add:
                            return cops.Add;
                        case Operator.Subtract:
                            return cops.Sub;
                        case Operator.Multiply:
                            return cops.Mul;
                        case Operator.Divide:
                            return cops.Div;
                    }
                }
            }
            //we didn't find a statically compiled Operations object - create a dynamic one
            return DynamicGenericMethodGenerator.GenerateNumericOperation<TLeft, TRight, TResult, TOwner>(op);
        }

        public static Delegate Lookup<TArg1, TResult>(Operator op)
        {
            INumericOperations ops;

            switch (op)
            {
                case Operator.Pow:
                    {
                        if (ConstructorCache.TryGetValue(typeof(TArg1), out ops))
                        {
                            INumericOperations<TArg1> cops = ops as INumericOperations<TArg1>;
                            if (cops != null)
                            {
                                return (Func<TArg1, Double, TArg1>)cops.Power;
                            }
                        }
                        break;
                    }
                case Operator.Log:
                case Operator.Log2:
                case Operator.Exp:
                case Operator.Abs:
                case Operator.Sqrt:
                    {
                        if (ConstructorCache.TryGetValue(typeof(TResult), out ops))
                        {
                            INumericOperations<TResult> cops = ops as INumericOperations<TResult>;
                            if (cops != null)
                            {
                                if (op == Operator.Abs)
                                {
                                    return (Func<TResult, TResult>)cops.Abs;
                                }
                                if (op == Operator.Log)
                                {
                                    return (Func<TResult, TResult>)cops.Exp;
                                }
                                if (op == Operator.Log2)
                                {
                                    return (Func<TResult, Double, TResult>)cops.Log;
                                }
                                if (op == Operator.Exp)
                                {
                                    return (Func<TResult, TResult>)cops.Exp;
                                }
                                if (op == Operator.Sqrt)
                                {
                                    return (Func<TResult, TResult>)cops.Sqrt;
                                }
                            }
                        }

                        break;
                    }


                case Operator.FromDecimal:
                case Operator.FromDouble:
                case Operator.FromInt16:
                case Operator.FromInt32:
                case Operator.FromInt64:
                case Operator.FromSingle:
                    {
                        if (ConstructorCache.TryGetValue(typeof(TResult), out ops))
                        {
                            INumericOperations<TResult> cops = ops as INumericOperations<TResult>;
                            if (cops != null)
                            {
                                if (typeof(TArg1) == typeof(Int16))
                                {
                                    return (Func<Int16, TResult>)cops.FromInt16;
                                }

                                if (typeof(TArg1) == typeof(Int32))
                                {
                                    return (Func<Int32, TResult>)cops.FromInt32;
                                }

                                if (typeof(TArg1) == typeof(Int64))
                                {
                                    return (Func<Int64, TResult>)cops.FromInt64;
                                }

                                if (typeof(TArg1) == typeof(Single))
                                {
                                    return (Func<Single, TResult>)cops.FromSingle;
                                }

                                if (typeof(TArg1) == typeof(Double))
                                {
                                    return (Func<Double, TResult>)cops.FromDouble;
                                }
                                if (typeof(TArg1) == typeof(Decimal))
                                {
                                    return (Func<Decimal, TResult>)cops.FromDecimal;
                                }
                            }
                        }
                        break;
                    }
                case Operator.ToDecimal:
                case Operator.ToDouble:
                case Operator.ToInt16:
                case Operator.ToInt32:
                case Operator.ToInt64:
                case Operator.ToSingle:
                    {
                        if (ConstructorCache.TryGetValue(typeof(TArg1), out ops))
                        {
                            INumericOperations<TArg1> cops = ops as INumericOperations<TArg1>;
                            if (cops != null)
                            {
                                if (typeof(TResult) == typeof(Int16))
                                {
                                    return (Func<TArg1, Int16>)cops.ToInt16;
                                }
                                if (typeof(TResult) == typeof(Int32))
                                {
                                    return (Func<TArg1, Int32>)cops.ToInt32;
                                }

                                if (typeof(TResult) == typeof(Int64))
                                {
                                    return (Func<TArg1, Int64>)cops.ToInt64;
                                }

                                if (typeof(TResult) == typeof(Single))
                                {
                                    return (Func<TArg1, Single>)cops.ToSingle;
                                }

                                if (typeof(TResult) == typeof(Double))
                                {
                                    return (Func<TArg1, Double>)cops.ToDouble;
                                }
                                if (typeof(TResult) == typeof(Decimal))
                                {
                                    return (Func<TArg1, Decimal>)cops.ToDecimal;
                                }
                            }
                        }

                        break;
                    }
            }
            return DynamicGenericMethodGenerator.GenerateMethod<TArg1, TResult>(op);
        }

        #region Nested type: OperatorCacheKey

        internal struct OperatorCacheKey
        {
            public Type Left;
            public Type Result;
            public Type Right;
        }

        #endregion
    }
}